package de.hpi.generator.helpers;

import de.hpi.generator.config.NotExistsException;
import java.util.HashMap;


import de.hpi.generator.model.Column;
import de.hpi.generator.model.ColumnsCollection;
import de.hpi.generator.model.Database;
import de.hpi.generator.model.Table;
import de.hpi.generator.model.dependencies.InclusionDependency;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class DatabaseResultValidator {

    public static boolean validateDatabaseValues(Database aDatabase) throws Exception {
        HashMap<Column, String[]> aAllColumnToValuesMapping = new HashMap<Column, String[]>();

        for (Table aTable : aDatabase.getTables()) {
            String aEtalonTableFileName = ".\\docs\\etalons\\FileTests\\spliterTest.csv";
            String aCurrentTableFileName = ".\\docs\\tests\\spliterTest.csv";
            FileHelper.copyFile(aEtalonTableFileName, aCurrentTableFileName);
            String[][] aTableValues = FileSpliter.splitTableFileToMemory(aCurrentTableFileName);
            aAllColumnToValuesMapping.putAll(assignColumnsToValues(aTable, aTableValues));
        }

        return validateResults(aAllColumnToValuesMapping, aDatabase);

    }

    protected static HashMap<Column, String[]> assignColumnsToValues(Table aTable, String[][] aTableValues) throws NotExistsException {

        HashMap<Column, String[]> aColumnToValuesMapping = new HashMap<Column, String[]>();

        for (String[] aColumnValues : aTableValues) {
            Column aColumn = aTable.findColumnByName(aColumnValues[0]);
            if (aColumn == null) {
                throw new NotExistsException(String.format("A column %s was not found in the table", aColumnValues[0]));
            }
            aColumnValues[0] = ""; // remove column name
            aColumnToValuesMapping.put(aColumn, aColumnValues);
        }

        return aColumnToValuesMapping;
    }

    protected static boolean validateResults(HashMap<Column, String[]> aColumnToValuesMapping, Database aDatabase) {

        boolean result = true;
        List<String> alreadyCheckedDependencies = new ArrayList<String>();
        for (Column aReferencedColumn : aDatabase.getAllColumns()) {

            if (!aReferencedColumn.getInclusionDependencyCollection().isEmpty()) {
                for (InclusionDependency aInclusionDependency : aReferencedColumn.getInclusionDependencyCollection()) {
                    if (alreadyCheckedDependencies.contains(aInclusionDependency.toString())) {
                        continue;
                    }

                    if (!validateInclusionDependency(aInclusionDependency, aColumnToValuesMapping)) {
                        System.err.println("validation of " + aInclusionDependency + " is failed\n");
                        return false;
                    } else {
                        System.out.println("validation of " + aInclusionDependency + " is OK");
                        alreadyCheckedDependencies.add(aInclusionDependency.toString());
                    }
                }
            }
        }
        return result;
    }

    protected static boolean validateInclusionDependency(InclusionDependency aInclusionDependency, HashMap<Column, String[]> aColumnToValuesMapping) {
        // is unary
        if (aInclusionDependency.getDependent() != null) {
            return validateUnaryInclusionDependency(aInclusionDependency, aColumnToValuesMapping);
        } else {
            return validateCompositeInclusionDependency(aInclusionDependency, aColumnToValuesMapping);
        }

    }

    protected static boolean validateUnaryInclusionDependency(InclusionDependency aInclusionDependency, HashMap<Column, String[]> aColumnToValuesMapping) {
        String[] aDependentValues = aColumnToValuesMapping.get(aInclusionDependency.getDependent());
        String[] aReferencedValues = aColumnToValuesMapping.get(aInclusionDependency.getReferenced());
        return isDependentInReferenced(Arrays.asList(aDependentValues), Arrays.asList(aReferencedValues));
    }

    protected static boolean validateCompositeInclusionDependency(InclusionDependency aInclusionDependency, HashMap<Column, String[]> aColumnToValuesMapping) {

        ArrayList<String[]> aDependentColumnValues = selectValuesOfColumns(aInclusionDependency.getDependentColumns(), aColumnToValuesMapping);
        ArrayList<String[]> aReferencedColumnValues = selectValuesOfColumns(aInclusionDependency.getReferencedColumns(), aColumnToValuesMapping);

        return isDependentInReferenced(joinAllValuesToLines(aDependentColumnValues), joinAllValuesToLines(aReferencedColumnValues));
    }

    protected static ArrayList<String[]> selectValuesOfColumns(ColumnsCollection aColumnsCollection, HashMap<Column, String[]> aColumnToValuesMapping) {
        ArrayList<String[]> aColumnValues = new ArrayList<String[]>();

        for (Column aColumn : aColumnsCollection) {
            aColumnValues.add(aColumnToValuesMapping.get(aColumn));
        }

        return aColumnValues;
    }

    protected static ArrayList<String> joinAllValuesToLines(ArrayList<String[]> aValuesCollection) {
        ArrayList<String> result = new ArrayList<String>();
        for (int aRowNumber = 1; aRowNumber < aValuesCollection.get(0).length; aRowNumber++) {
            String aLine = joinRowValuesToLine(aValuesCollection, aRowNumber);
            result.add(aLine);
        }
        return result;
    }

    protected static String joinRowValuesToLine(ArrayList<String[]> aValuesCollection, int aRowNumber) {
        StringBuilder result = new StringBuilder();

        for (int aColumnNumber = 0; aColumnNumber < aValuesCollection.size(); aColumnNumber++) {
            if (result.length() > 0) {
                result.append("-");
            }
            result.append(aValuesCollection.get(aColumnNumber)[aRowNumber]);
        }
        return result.toString();

    }

    protected static boolean isDependentInReferenced(List<String> aDependentValues, List<String> aReferencedValues) {

        List<String> alreadyFound = new ArrayList<String>();

        for (String aDependentValue : aDependentValues) {
            if (alreadyFound.contains(aDependentValue)) {
                continue;
            }
            if (!aReferencedValues.contains(aDependentValue)) {
                return false;
            }
            alreadyFound.add(aDependentValue);
        }
        return true;
    }
}
